package com.xyy.saas.payment.merchant.core.service;

import com.google.common.collect.Lists;
import com.xyy.saas.payment.adpater.RouteManager;
import com.xyy.saas.payment.callback.core.dto.CallbackMsg;
import com.xyy.saas.payment.callback.core.result.CallbackRunner;
import com.xyy.saas.payment.common.Constants;
import com.xyy.saas.payment.common.constants.ApolloConstants;
import com.xyy.saas.payment.common.exception.PaymentException;
import com.xyy.saas.payment.cores.api.PaymentOpenAccountApi;
import com.xyy.saas.payment.cores.bo.AccountModifyBo;
import com.xyy.saas.payment.cores.enums.AccountCreateStatusEnum;
import com.xyy.saas.payment.cores.enums.AccountModifyStatusEnum;
import com.xyy.saas.payment.cores.enums.ContactEnum;
import com.xyy.saas.payment.cores.enums.ResultCodeEnum;
import com.xyy.saas.payment.cores.param.QueryAccountModifyParam;
import com.xyy.saas.payment.cores.vo.AccountUpdateVo;
import com.xyy.saas.payment.cores.vo.CallbackResultVo;
import com.xyy.saas.payment.cores.vo.ContactInfo;
import com.xyy.saas.payment.cores.vo.ResultVO;
import com.xyy.saas.payment.dao.model.ApplyAccountModify;
import com.xyy.saas.payment.dao.model.UserInfo;
import com.xyy.saas.payment.merchant.core.dto.AccountModifyNoticeDto;
import com.xyy.saas.payment.merchant.core.dto.MerchantAccountModifyDto;
import com.xyy.saas.payment.merchant.core.dto.PersonalAccountModifyDto;
import com.xyy.saas.payment.merchant.core.dto.UserInfoDto;
import com.xyy.saas.payment.merchant.core.vo.SettleAccountDto;
import com.xyy.saas.payment.merchant.core.vo.UserDto;
import com.xyy.saas.payment.merchant.core.vo.UserInfoVo;
import com.xyy.saas.payment.service.ApplyAccountModifyService;
import com.xyy.saas.payment.service.PayAccountService;
import com.xyy.saas.payment.service.UserInfoService;
import com.xyy.saas.payment.util.*;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import util.AESUtil;

import javax.annotation.Resource;
import java.util.List;

@Service
@Slf4j
public class AccountModifyService {
    @Resource
    private PayAccountService payAccountService;
    @Resource
    private ApplyAccountModifyService applyAccountModifyService;
    @Resource
    private LeafUtil leafUtil;
    @Resource
    private RedisUtil redisUtil;
    @Resource
    private RouteManager routeManager;
    @Resource
    private UserInfoService2 userInfoService2;
    @Resource
    private UserInfoService userInfoService;
    @Resource
    private CallbackRunner callbackRunner;
    @Autowired
    private AccountService accountService;
    @Resource
    private PaymentOpenAccountApi paymentOpenAccountApi;

    @SneakyThrows
    public void updateUserInfoAndPinganAccount(UserInfoDto dto) {
        String accountId = payAccountService.getAccountIdByBusiness(dto.getBusinessIdType(), dto.getBusinessUserId());
        if (StringUtils.isBlank(accountId)) {
            throw new PaymentException(ResultCodeEnum.USER_NOT_FOUND);
        }
        UserInfo oldUserInfo = userInfoService.queryUserInfoByAccountId(accountId);
        if (oldUserInfo == null) {
            throw new PaymentException(ResultCodeEnum.USER_NOT_FOUND);
        }
        UserInfo updateUserInfo = new UserInfo();
        AccountUpdateVo accountUpdateVo = new AccountUpdateVo();
        encryptAndFill(updateUserInfo, accountUpdateVo, oldUserInfo, dto);
        // 更新支付商户信息
        userInfoService.updateUserInfoByUserId(updateUserInfo);
        // 更新平安商户信息
        paymentOpenAccountApi.updateAccount(accountUpdateVo);
    }

    @SneakyThrows
    private void encryptAndFill(UserInfo updateUserInfo, AccountUpdateVo accountUpdateVo, UserInfo oldUserInfo, UserInfoDto dto) {
        String userInfoKey = ApolloUtil.getProperty(ApolloConstants.USER_INFO_KEY);

        // 解密基本信息和结算信息
        UserDto basicInfo = decryptAndParse(oldUserInfo.getBasicInfo(), userInfoKey, UserDto.class);
        SettleAccountDto settleInfo = decryptAndParse(oldUserInfo.getSettleInfo(), userInfoKey, SettleAccountDto.class);
        List<ContactInfo> contactInfoVoList = decryptAndParseList(oldUserInfo.getContactInfo(), userInfoKey, ContactInfo.class);

        // 法人信息
        ContactInfo legalPersonInfo = contactInfoVoList.stream()
                .filter(x -> ContactEnum.LEGAL_PERSON.getCode().equals(x.getContactType()))
                .findFirst()
                .orElseThrow(() -> new PaymentException(ResultCodeEnum.USER_NOT_FOUND, "法人不存在"));

        // 更新法人信息
        updateLegalPersonInfo(legalPersonInfo, dto);

        // 更新业务用户信息
        updateBusinessUserInfo(basicInfo, settleInfo, dto);

        // 加密并填充更新的用户信息
        updateUserInfo.setAccountId(oldUserInfo.getAccountId());
        updateUserInfo.setBasicInfo(encryptAndConvertToJson(basicInfo, userInfoKey));
        updateUserInfo.setSettleInfo(encryptAndConvertToJson(settleInfo, userInfoKey));
        updateUserInfo.setContactInfo(encryptAndConvertToJson(contactInfoVoList, userInfoKey));

        // 填充 AccountUpdateVo 信息
        fillAccountUpdateVo(accountUpdateVo, oldUserInfo, dto, legalPersonInfo, basicInfo);
    }

    private void fillAccountUpdateVo(AccountUpdateVo accountUpdateVo, UserInfo oldUserInfo, UserInfoDto dto, ContactInfo legalPersonInfo, UserDto basicInfo) {
        boolean identityParamValid = StringUtils.isNotBlank(dto.getIdNo()) && StringUtils.isNotBlank(dto.getName());
        String reprName = identityParamValid ? dto.getName() : legalPersonInfo.getContactName();
        String reprGlobalId = identityParamValid ? dto.getIdNo() : legalPersonInfo.getContactCertNo();

        accountUpdateVo.setAccountId(oldUserInfo.getAccountId())
                .setMemberName(dto.getBusinessUserName())
                .setCompanyName(dto.getBusinessUserName())
                .setReprName(reprName)
                .setReprGlobalId(reprGlobalId)
                .setCompanyGlobalType("73") // 对接平安固定值
                .setCompanyGlobalId(basicInfo.getIdentityInfo().getId());
    }

    private void updateBusinessUserInfo(UserDto basicInfo, SettleAccountDto settleInfo, UserInfoDto dto) {
        String businessUserName = dto.getBusinessUserName();
        if (StringUtils.isNotBlank(businessUserName)) {
            basicInfo.setName(businessUserName);
            settleInfo.setBusinessUserName(businessUserName);
            settleInfo.getRecipient().setName(businessUserName);
        }
    }

    private void updateLegalPersonInfo(ContactInfo legalPersonInfo, UserInfoDto dto) {
        if (StringUtils.isNotBlank(dto.getIdNo()) && StringUtils.isNotBlank(dto.getName())) {
            legalPersonInfo.setContactName(dto.getName());
            legalPersonInfo.setContactCertNo(dto.getIdNo());
        }
    }

    @SneakyThrows
    private static <T> List<T> decryptAndParseList(String data, String key, Class<T> clazz) {
        try {
            if (StringUtils.isBlank(data)) {
                return Lists.newArrayList();
            }
            String decryptedData = AESUtil.decrypt(data, key);
            return JSONUtils.parse2List(decryptedData, clazz);
        } catch (Exception e) {
            log.error("Error during decryption and parsing. e:", e);
            throw new PaymentException(ResultCodeEnum.DEFAULT_ERROR);
        }
    }

    @SneakyThrows
    private <T> T decryptAndParse(String data, String key, Class<T> clazz) {
        try {
            String decryptedData = AESUtil.decrypt(data, key);
            return JSONUtils.parse2Object(decryptedData, clazz);
        } catch (Exception e) {
            log.error("Error during decryption and parsing. e:", e);
            throw new PaymentException(ResultCodeEnum.DEFAULT_ERROR);
        }
    }

    private String encryptAndConvertToJson(Object data, String key) throws PaymentException {
        try {
            String jsonData = JSONUtils.toJSON(data);
            return AESUtil.encrypt(jsonData, key);
        } catch (Exception e) {
            log.error("Error during encryption and JSON conversion. e:", e);
            throw new PaymentException(ResultCodeEnum.DEFAULT_ERROR);
        }
    }

    /**
     * 修改企业账户信息
     */
    public ResultVO<AccountModifyBo> modifyMerchant(MerchantAccountModifyDto merchantParam) throws Exception {
        log.info("AccountService#modifyMerchant modify start. MerchantAccountModifyDto:{}", JSONUtils.toJSON(merchantParam));

        // 1.参数校验
        merchantParam.checkParam();

        // 2.重复提交检查
        String accountId = payAccountService.getAccountIdByBusiness(merchantParam.getBusinessIdType().getType(), merchantParam.getBusinessId());

        if(StringUtils.isBlank(accountId)) {
            throw new PaymentException(ResultCodeEnum.USER_NOT_FOUND);
        }

        repeatCheck(accountId);

        // 3.补充商户信息
        merchantParam.setAccountId(accountId);

        // 业务传单号，优先用业务单号
        if(StringUtils.isBlank(merchantParam.getApplyNo())) {
            merchantParam.setApplyNo(leafUtil.getSnowflakeId());
        }

        ApplyAccountModify applyAccountModify = merchantParam.transfer();
        AccountModifyStatusEnum status = AccountModifyStatusEnum.AUDITING;
        AccountModifyBo accountModifyBo = null;
        Exception exception = null;

        // 4.本地存储修改申请
        if(applyAccountModifyService.insertSelective(applyAccountModify)) {
            try {
                // 5.提交渠道账户变更申请
                accountModifyBo = routeManager.modifyMerchant(merchantParam);
            }
            catch(Exception ex) {
                log.error("AccountService#modifyMerchant error. message=" +
                        ex.getMessage() + ", param=" + JSONUtils.toJSON(merchantParam), ex);
                status = AccountModifyStatusEnum.FAIL;
                exception = ex;
            }
        }

        // 6.更新申请单
        applyAccountModify.setStatus(status.getStatus());
        applyAccountModify.setMessage(exception instanceof PaymentException ? ((PaymentException) exception).getErrMsg() : status.getDesc());
        applyAccountModifyService.updateStatusByPrimary(applyAccountModify);

        // 7.返回响应
        if(exception != null) {
            throw exception;
        }

        return ResultVO.createSuccess(accountModifyBo);
    }

    /**
     * 修改个人账户信息
     */
    public ResultVO<AccountModifyBo> modifyPersonal(PersonalAccountModifyDto personalParam) throws Exception {
        log.info("AccountService#modifyPersonal modify start. PersonalAccountModifyDto:{}", JSONUtils.toJSON(personalParam));

        // 1.参数校验
        personalParam.checkParam();

        // 2.重复提交检查
        String accountId = payAccountService.getAccountIdByBusiness(personalParam.getBusinessIdType().getType(), personalParam.getBusinessId());

        if(StringUtils.isBlank(accountId)) {
            throw new PaymentException(ResultCodeEnum.USER_NOT_FOUND);
        }

        repeatCheck(accountId);

        // 3.补充商户信息
        personalParam.setAccountId(accountId);

        // 业务传单号，优先用业务单号
        if(StringUtils.isBlank(personalParam.getApplyNo())) {
            personalParam.setApplyNo(leafUtil.getSnowflakeId());
        }

        ApplyAccountModify applyAccountModify = personalParam.transfer();
        AccountModifyStatusEnum status = AccountModifyStatusEnum.AUDITING;
        AccountModifyBo accountModifyBo = null;
        Exception exception = null;

        // 4.本地存储修改申请
        if(applyAccountModifyService.insertSelective(applyAccountModify)) {
            try {
                // 5.提交渠道账户变更申请
                accountModifyBo = routeManager.modifyPersonal(personalParam);
            }
            catch(Exception ex) {
                log.error("AccountService#modifyPersonal error. message=" +
                        ex.getMessage() + ", param=" + JSONUtils.toJSON(personalParam), ex);
                status = AccountModifyStatusEnum.FAIL;
                exception = ex;
            }
        }

        // 6.更新本都申请单
        applyAccountModify.setStatus(status.getStatus());
        applyAccountModify.setMessage(exception instanceof PaymentException ? ((PaymentException) exception).getErrMsg() : status.getDesc());
        applyAccountModifyService.updateStatusByPrimary(applyAccountModify);

        // 7.返回响应
        if(exception != null) {
            throw exception;
        }

        return ResultVO.createSuccess(accountModifyBo);
    }

    /**
     * 查询企业账户信息
     */
    public MerchantAccountModifyDto queryModifyMerchant(QueryAccountModifyParam queryParam) throws Exception {
        log.info("AccountService#queryModifyMerchant start. QueryAccountModifyParam:{}", JSONUtils.toJSON(queryParam));

        // 1.参数校验
        if((StringUtils.isBlank(queryParam.getBusinessId()) && StringUtils.isBlank(queryParam.getApplyNo())) || queryParam.getBusinessIdType() == null) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
        }

        ApplyAccountModify applyAccountModify = null;

        if(StringUtils.isBlank(queryParam.getApplyNo())) {
            String accountId = payAccountService.getAccountIdByBusiness(queryParam.getBusinessIdType().getType(), queryParam.getBusinessId());

            if(StringUtils.isBlank(accountId)) {
                throw new PaymentException(ResultCodeEnum.USER_NOT_FOUND);
            }

            // 2.查询数据
            applyAccountModify = applyAccountModifyService.queryApplyInfoByAccountId(accountId, Constants.SOURCE_MODIFY_ACCOUNT);
        }
        else {
            applyAccountModify = applyAccountModifyService.queryApplyInfoByApplyNo(queryParam.getApplyNo());
        }

        if(applyAccountModify == null) {
            return null;
        }

        MerchantAccountModifyDto result = MerchantAccountModifyDto.transfer(applyAccountModify);
        result.setBusinessId(payAccountService.getBusinessUserIdByAccountId(applyAccountModify.getAccountId()));
        return result;
    }

    /**
     * 查询企业账户信息列表
     */
    public List<MerchantAccountModifyDto> queryListModifyMerchant(QueryAccountModifyParam queryParam) throws Exception {
        log.info("AccountService#queryListModifyMerchant start. QueryAccountModifyParam:{}", JSONUtils.toJSON(queryParam));

        // 1.参数校验
        if(StringUtils.isBlank(queryParam.getBusinessId()) || queryParam.getBusinessIdType() == null) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
        }

        String accountId = payAccountService.getAccountIdByBusiness(queryParam.getBusinessIdType().getType(), queryParam.getBusinessId());

        if(StringUtils.isBlank(accountId)) {
            throw new PaymentException(ResultCodeEnum.USER_NOT_FOUND);
        }

        List<ApplyAccountModify> items = applyAccountModifyService.queryApplyInfoListByAccountId(accountId, Constants.SOURCE_MODIFY_ACCOUNT);
        return MerchantAccountModifyDto.transfer(items, queryParam.getBusinessId());
    }

    /**
     * 处理账户修改审核结果
     */
    public void dealAuditResult(AccountModifyNoticeDto noticeDto) throws Exception {
        log.info("AccountService#dealAuditResult start. AccountModifyNoticeDto:{}", JSONUtils.toJSON(noticeDto));

        // 1.更新申请单状态
        ApplyAccountModify applyAccountModify = applyAccountModifyService.queryApplyInfoByApplyNo(noticeDto.getApplyNo());

        if(applyAccountModify == null) {
            throw new PaymentException(ResultCodeEnum.ORDER_NOT_EXIST);
        }

        AccountModifyStatusEnum auditResult = noticeDto.auditPass() ? AccountModifyStatusEnum.PASS : AccountModifyStatusEnum.REJECT;
        String message = noticeDto.auditPass() ? auditResult.getDesc() : noticeDto.getMsg();

        if(auditResult.getStatus().equals(applyAccountModify.getStatus())) {
            log.info("AccountService#dealAuditResult ignore，No change in status. AccountModifyNoticeDto:{}", JSONUtils.toJSON(noticeDto));
            return;
        }

        applyAccountModify.setStatus(auditResult.getStatus());
        applyAccountModify.setMessage(message);
        applyAccountModifyService.updateStatusByPrimary(applyAccountModify);

        // 2.更新开户信息
        if(noticeDto.auditPass()) {
            UserInfoVo userInfoVo = userInfoService2.queryUserInfo(applyAccountModify.getAccountId());

            if(Constants.MERCHANT.equals(applyAccountModify.getType())) {
                MerchantAccountModifyDto merchantAccountModifyDto = MerchantAccountModifyDto.transfer(applyAccountModify);
                merchantAccountModifyDto.copy2UserInfo(userInfoVo);
            }
            else {
                PersonalAccountModifyDto personalAccountModifyDto = PersonalAccountModifyDto.transfer(applyAccountModify);
                personalAccountModifyDto.copy2UserInfo(userInfoVo);
            }

            userInfoService2.updateUserInfo(userInfoVo);
        }

        String tradeNo = payAccountService.getBusinessUserIdByAccountId(applyAccountModify.getAccountId());

        // 3.通知业务
        // 3.1修改入口提交，直接通知
        if(Constants.SOURCE_MODIFY_ACCOUNT.equals(applyAccountModify.getSource())) {
            // 用修改type通知
            String type = noticeDto.auditPass() ? CallbackResultVo.ACCOUNT_MODIFY_AUDIT_PASS : CallbackResultVo.ACCOUNT_MODIFY_AUDIT_REJECT;
            callbackRunner.callback(CallbackMsg.build(type, applyAccountModify.getCallback(), tradeNo, applyAccountModify.getApplyNo(), message, DateUtil.now().getTime()));
        }
        // 3.2进件入口提交，需看整体审核状态和进件状态
        else {
            if(accountService.checkOpenAccountComplete(applyAccountModify.getAccountId())) {
                String type = noticeDto.auditPass() ? CallbackResultVo.TYPE_OPEN_ACCOUNT_SUCCESS : CallbackResultVo.TYPE_OPEN_ACCOUNT_FAIL;
                callbackRunner.callback(CallbackMsg.build(type, tradeNo, applyAccountModify.getCallback(), message));
            }
            else if(accountService.checkOpenAccountWaitAuth(applyAccountModify.getAccountId())) {
                String type = noticeDto.auditPass() ? CallbackResultVo.APPLYMENT_STATE_WAITTING_FOR_CONFIRM_CONTACT : CallbackResultVo.TYPE_OPEN_ACCOUNT_FAIL;
                callbackRunner.callback(CallbackMsg.build(type, tradeNo, applyAccountModify.getCallback(), message));
            }
            else if(!noticeDto.auditPass() && accountService.checkOpenAccountWaitAudit(applyAccountModify.getAccountId())) {
                callbackRunner.callback(CallbackMsg.build(CallbackResultVo.TYPE_OPEN_ACCOUNT_FAIL, tradeNo, applyAccountModify.getCallback(), message));
            }
            else {
                log.info("AccountService#dealAuditResult No notice for the time being. AccountModifyNoticeDto:{}", JSONUtils.toJSON(noticeDto));
            }
        }

        log.info("AccountService#dealAuditResult end. AccountModifyNoticeDto:{}", JSONUtils.toJSON(noticeDto));
    }

    /**
     * 是否需要通知业务开户完成
     * 1.无变更中的申请单
     * 2.基本户未被驳回
     */
    public boolean needNoticeOpenSuccess(String accountId) {
        ApplyAccountModify applyAccountModify = applyAccountModifyService.queryApplyInfoByAccountId(accountId, Constants.SOURCE_OPEN_ACCOUNT);

        if(applyAccountModify != null && !AccountModifyStatusEnum.PASS.getStatus().equals(applyAccountModify.getStatus())) {
            return false;
        }

        UserInfo userInfo = userInfoService.queryUserInfoByAccountId(accountId);

        if(userInfo != null && !AccountCreateStatusEnum.PASS.getStatus().equals(userInfo.getAuditStatus())) {
            return false;
        }

        return true;
    }

    /**
     * 校验：一个商户同时只能有一个进行中单据
     */
    private void repeatCheck(String accountId) throws PaymentException {
        // 5s内重复提交
        if(StringUtils.isNotBlank(redisUtil.get(RedisUtil.MODIFY_ACCOUNT_PREFIX + accountId))) {
            throw new PaymentException(ResultCodeEnum.ACCOUNT_MODIFY_PROCESSING);
        }

        redisUtil.set(RedisUtil.MODIFY_ACCOUNT_PREFIX + accountId, RedisUtil.SECOND_5);

        // 同一商户串行校验
        ApplyAccountModify item = applyAccountModifyService.queryApplyInfoByAccountId(accountId, StringUtils.EMPTY);

        if(item != null && !AccountModifyStatusEnum.canCreate(item.getStatus())) {
            throw new PaymentException(ResultCodeEnum.ACCOUNT_MODIFY_AUDITING);
        }
    }
}
